package com.spatio.serving.basic.service.template.core.component.invoker;

import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Strings;
import com.spatio.serving.basic.service.template.common.req.OssCompleteReq;
import com.spatio.serving.basic.service.template.common.vo.UrlUploadInfo;
import com.spatio.serving.basic.service.template.core.component.invoker.config.InvokeProperties;
import com.spatio.serving.common.lib.common.entity.ApiResponse;
import com.spatio.serving.common.lib.common.http.SpatioHttpClient;
import com.spatio.serving.common.lib.common.util.JsonUtil;
import com.spatio.serving.common.lib.common.util.LatencyWatch;
import com.spatio.serving.spring.boot.starter.prometheus.op.impl.HttpInvokePrometheusOp;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class OssInvoker {

    @Resource
    private InvokeProperties invokeProperties;
    private static final int OSS_TIME_OUT_MS = 500;
    @Resource
    private SpatioHttpClient spatioHttpClient;

    @Value("${url.oss.get.complete.url}")
    private String urlOssGetCompleteUrl;

    @Value("${url.oss.pre.signature}")
    private String urlOssPreSignature;


    @Value("${calories_url.oss.pre.signature}")
    private String calories_urlOssPreSignature;

    @Value("${url.oss.get.complete.url}")
    private String calories_urlOssGetCompleteUrl;

    public Map<String, String> getOssCompleteUrl3Times(List<String> uris) {
        for (int i = 0; i < 3; i++) {
            Map<String, String> resultMap = getOssCompleteUrl(uris);
            if (resultMap != null) {
                return resultMap;
            }
        }
        return null;
    }

    public Map<String, String> getOssCompleteUrl(List<String> uris) {
        OssCompleteReq req = new OssCompleteReq();
        req.setUris(uris);
        //String url = urlOssGetCompleteUrl;
        String url = calories_urlOssGetCompleteUrl;


        try {
            req.setHttpProtocol("https");
            HttpInvokePrometheusOp.op().qpsTotal("OssInvoker_getCompleteUrl");
            String param = JsonUtil.toJson(req);
            log.info("getCompleteUrl param:{}", param);
            long httpStart = System.currentTimeMillis();
            LatencyWatch watch = LatencyWatch.start();
            String responseStr = spatioHttpClient.postWithBody(url, param, null, OSS_TIME_OUT_MS);
            log.info("OssInvoker getCompleteUrl cost:{} result:{}", watch.cost(), responseStr);
            HttpInvokePrometheusOp.op().latency("OssInvoker_getCompleteUrl", (System.currentTimeMillis() - httpStart), TimeUnit.MILLISECONDS);
            HttpInvokePrometheusOp.op().qpsTotal("OssInvoker_getCompleteUrl");
            if (responseStr == null) {
                log.error("invoke url={}, http result body is null", url);
                return null;
            }
            ApiResponse response = JsonUtil.fromJson(responseStr, ApiResponse.class);
            if (response.getCode() != 0) {
                log.error("invoke url={}, code is error", url);
                return null;
            }
            return JsonUtil.fromJson(JsonUtil.toJson(response.getResult()), Map.class);
        } catch (Exception e) {
            log.error("OssInvoker_getCompleteUrl url={} exception", url, e);
            return null;
        }
    }

    public UrlUploadInfo upload(MultipartFile file, String region , String bizType , String contentType) throws IOException {
        // 读取文件内容
        //InputStream inputStream = Files.newInputStream(Paths.get(fileName));
        //byte[] readBytes = read(inputStream);
        byte[] readBytes = file.getBytes();
        byte[] digest = SecureUtil.md5().digest(readBytes);
        String contentMd5 = new String(Base64.encode(digest).getBytes(StandardCharsets.UTF_8));
        //String bizType = "calories-audio";
        //String contentType = audio/wav
        // 1) getSignature
        //String presignUrl = urlOssPreSignature;
        String presignUrl = calories_urlOssPreSignature;
        Map<String, String> param = new HashMap<>();
        param.put("bizType", bizType);
        param.put("contentType", contentType);
        param.put("md5", contentMd5);
        param.put("regionCode", region);
        param.put("appid", "calories");
        param.put("service", "local-oss");

        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(presignUrl);
            httpPost.setHeader("Content-Type", "application/json");
            httpPost.setEntity(new ByteArrayEntity(JsonUtil.toJson(param).getBytes(StandardCharsets.UTF_8)));
            LatencyWatch watch = LatencyWatch.start();
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                HttpEntity entity = response.getEntity();
                JSONObject responseJson = JsonUtil.fromJson(EntityUtils.toString(entity), JSONObject.class);
                JSONObject resultJson = responseJson.getJSONObject("result");

                UrlUploadInfo urlUploadInfo = JsonUtil.fromJson(resultJson.toString(), UrlUploadInfo.class);
                if (StringUtils.hasText(urlUploadInfo.getUri())) {
                    log.info("OssInvoker upload first getSignature result cost:{} uri:{}", watch.cost(), urlUploadInfo.getUri());

                    // 2）Upload
                    String etag = uploadAndGetEtag(readBytes, contentMd5, contentType, urlUploadInfo.getUrlForUpload(), httpClient);
                    if (Strings.isNullOrEmpty(etag)) {
                        throw new RuntimeException("upload fail");
                    }
                    log.info("OssInvoker upload second uploadAndGetEtag result cost:{} etag:{}", watch.cost(), etag);

                }
                return urlUploadInfo;
            }
        }
    }

    private String uploadAndGetEtag(byte[] bytes, String contentMd5, String contentType, String url, CloseableHttpClient httpClient) throws IOException {
        HttpPut httpPut = new HttpPut(url);
        httpPut.setHeader("Content-MD5", contentMd5);
        httpPut.setHeader("Content-Type", contentType);
        httpPut.setEntity(new ByteArrayEntity(bytes));
        CloseableHttpResponse response = httpClient.execute(httpPut);
        return response.getFirstHeader("Etag").getValue();
    }

    //Support Function
    private byte[] read(InputStream inputStream) throws IOException {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int num = inputStream.read(buffer);
            while (num != -1) {
                baos.write(buffer, 0, num);
                num = inputStream.read(buffer);
            }
            baos.flush();
            return baos.toByteArray();
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

}
